/* Copyright 2009-2016 David Hadka
 *
 * This file is part of the MOEA Framework.
 *
 * The MOEA Framework is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * The MOEA Framework is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with the MOEA Framework.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.moeaframework.algorithm;

import java.io.Serializable;
import java.util.Comparator;
import hh.moea.SteadyStateFastNonDominatedSorting;

import org.moeaframework.core.FastNondominatedSorting;
import org.moeaframework.core.FitnessEvaluator;
import org.moeaframework.core.Initialization;
import org.moeaframework.core.Population;
import org.moeaframework.core.Problem;
import org.moeaframework.core.Selection;
import org.moeaframework.core.Solution;
import org.moeaframework.core.Variation;
import org.moeaframework.core.comparator.ChainedComparator;
import org.moeaframework.core.comparator.FitnessComparator;
import org.moeaframework.core.comparator.NondominatedSortingComparator;
import org.moeaframework.core.comparator.RankComparator;
import org.moeaframework.core.operator.TournamentSelection;

/**
 * Implementation of the S-metric Selection MOEA (SMS-MOEA). The S metric is
 * also known as the hypervolume indicator.
 * <p>
 * References:
 * <ol>
 * <li>Emmerich, M., N. Beume, and B. Naujoks (2007). An EMO Algorithm Using the
 * Hypervolume Measure as Selection Criterion. European Journal of Operational
 * Research, 3:1653-1669.
 */
public class SMSEMOA extends AbstractEvolutionaryAlgorithm {

    /**
     * The fitness evaluator to use (e.g., hypervolume or additive-epsilon
     * indicator).
     */
    private FitnessEvaluator fitnessEvaluator;

    /**
     * The selection operator.
     */
    private Selection selection;

    /**
     * The variation operator.
     */
    private Variation variation;

    private SteadyStateFastNonDominatedSorting enlu;

    /**
     * Constructs a new SMS-EMOA instance.
     *
     * @param problem the problem
     * @param initialization the initialization operator
     * @param variation the variation operator
     * @param fitnessEvaluator the fitness evaluator
     */
    public SMSEMOA(Problem problem, Initialization initialization,
            Variation variation, FitnessEvaluator fitnessEvaluator) {
        super(problem,
                new Population(),
                null,
                initialization);
        this.variation = variation;
        this.fitnessEvaluator = fitnessEvaluator;

        if (fitnessEvaluator == null) {
            selection = new TournamentSelection(
                    new NondominatedSortingComparator());
        } else {
            selection = new TournamentSelection(
                    new NondominatedFitnessComparator());
        }
    }

    @Override
    protected void initialize() {
        super.initialize();

        // rank the solutions
        new FastNondominatedSorting().evaluate(population);

//		if (fitnessEvaluator != null) {
//			fitnessEvaluator.evaluate(population);
//		}
        enlu = new SteadyStateFastNonDominatedSorting();
        computeFitnessForLastFront();
    }

    @Override
    protected void iterate() {
        int populationSize = population.size();
        Solution[] parents = selection.select(variation.getArity(), population);
        Solution[] offspring = variation.evolve(parents);

        evaluate(offspring[0]);
//		population.add(offspring[0]);
//		
//		// rank the solutions and remove the worst
//		new FastNondominatedSorting().evaluate(population);
        enlu.addSolution(offspring[0], population);

        if (fitnessEvaluator == null) {
            population.truncate(populationSize,
                    new NondominatedSortingComparator());
        } else {
            computeFitnessForLastFront();

            population.truncate(populationSize,
                    new NondominatedFitnessComparator());
        }
    }

    /**
     * Computes the fitness for the last front.
     */
    private void computeFitnessForLastFront() {
        Population front = new Population();
//		int rank = 0;
//		
        for (Solution solution : population) {
//			int solutionRank = (Integer)solution.getAttribute(
//					FastNondominatedSorting.RANK_ATTRIBUTE);
//			
//			if (solutionRank > rank) {
//				front.clear();
//				rank = solutionRank;
//			}
//			
//			if (solutionRank == rank) {
//				front.add(solution);
//			}
////			
            solution.setAttribute(FitnessEvaluator.FITNESS_ATTRIBUTE, 0.0);
        }
        if (enlu.getLastFront() != null) {
            for (Integer i : enlu.getLastFront()) {
                Solution solution = population.get(i);
                front.add(solution);
            }

            fitnessEvaluator.evaluate(front);
        }
    }

    private class NondominatedFitnessComparator extends ChainedComparator
            implements Comparator<Solution>, Serializable {

        private static final long serialVersionUID = -4088873047790962685L;

        public NondominatedFitnessComparator() {
            super(new RankComparator(), new FitnessComparator(
                    fitnessEvaluator.areLargerValuesPreferred()));
        }

    }

}
